﻿namespace Warehouse

open System
open System.Windows    
open System.Windows.Controls
open System.Windows.Controls.Primitives
open System.Windows.Input
open System.Collections.ObjectModel
open FSharp.Windows

type EventWithValueAndCancel<'T> = 'T * (unit -> unit)
type CancelableEvent = (unit -> unit)

module Event = 
    let mapToValueAndCancel (ctor : EventWithValueAndCancel<'a> -> 'b) (contentMapper : #RoutedEventArgs -> 'a) (events : IEvent<_, #RoutedEventArgs>) :  IObservable<'b> =
        events |> Observable.map (fun event -> ctor(contentMapper event, fun() -> event.Handled <- true))  
    
    let mapToCancelable (ctor : CancelableEvent -> 'a) (events : IEvent<_, #RoutedEventArgs>) :  IObservable<'a> = 
        events |> Observable.map ( fun event -> ctor <| fun() -> event.Handled <- true ) 

module Decimal =
    let inline round (places : int) x = Decimal.Round(x, places)
    let round2 = round 2
    let canBeParsedFrom s = s = "-"  || (s |> Decimal.TryParse |> fst)

[<AutoOpen>]
module Extensions = 

    let isNull x = x = null 
    let isNotNull x = x <> null

    type Nullable<'T when 'T : (new : unit -> 'T) and 'T : struct and 'T :> ValueType> with
        member this.AsOption() = if this.HasValue then Some this.Value else None

    let inline (|GreaterThan|_|) value x = if x > value then Some() else None
    let inline (|LessThan|_|) value x = if x < value then Some() else None
    let inline (|Equal|_|) value x = if x = value then Some() else None
    let inline (|GreaterOrEqualThan|_|) value = function | GreaterThan value | Equal value -> Some() | _ -> None
    let inline (|LessOrEqualThan|_|) value = function | LessThan value | Equal value -> Some() | _ -> None
    let inline (|Between|_|) start end' = function | GreaterOrEqualThan start & LessOrEqualThan end' -> Some() | _ -> None

    let (=~) s1 s2 = String.Compare(s1, s2, false) = 0

    type ObservableCollection<'T> with
        static member ofSeq (xs : 'T seq) = ObservableCollection xs

    type TextBox with
        member this.EnsureValidInput(isTextValid : string -> bool) = 
            this.PreviewTextInput.Add <| fun eventArgs ->  
                let notValidText = not << isTextValid
                eventArgs.Handled <- notValidText <| this.Text + eventArgs.Text

        member this.EnsureInt() = this.EnsureValidInput(Int32.TryParse >> fst)
        member this.EnsurePositiveInt() = 
            this.EnsureValidInput <| fun input ->
                match Int32.TryParse input with 
                | true, x when x > 0 -> true
                | _ -> false
        member this.EnsureDecimal() = this.EnsureValidInput Decimal.canBeParsedFrom
        member this.EnsurePositiveDecimal() = 
            this.EnsureValidInput <| fun input ->
                match Decimal.TryParse input with 
                | true, x when x > 0M -> true
                | _ -> false

    type ButtonBase with
        static member ClickToValue(button : ButtonBase, value) = button.Click |> Observable.mapTo value
        member this.ClickToValue(value) = this.Click |> Observable.mapTo value

    type MenuItem with 
        static member ClickToValue(menuItem : MenuItem, value) = menuItem.Click |> Observable.mapTo value

    type DataGrid with
        member grid.DoubleClickRowEvent() = 
            let event = Event<_>()
            let trigger = MouseButtonEventHandler(fun _ _ -> if grid.SelectedItem <> null then event.Trigger())
            grid.AddHandler(DataGridRow.MouseDoubleClickEvent, trigger)
            event.Publish

    type Selector with
        member this.SynchronizedWithCurrentItem() = this.IsSynchronizedWithCurrentItem <- Nullable true
        member this.SelectionChangedAndNotNull<'T>() = 
            this.SelectionChanged |> Observable.choose (fun event -> 
                if event.AddedItems.Count > 0 then Some event else None
            )

    type DataTemplateSelector with
        static member Create(selector : 'a -> string option) = 
            {
                new DataTemplateSelector() with
                    member this.SelectTemplate(item, container) =
                        match container, item  with
                        | (:? FrameworkElement as element),(:? 'a as x) ->
                            match selector x with 
                            | Some resourse -> element.FindResource(resourse) |> unbox
                            | None -> DataTemplate()
                        | _ -> DataTemplate()
            }

    type MessageBox with
        static member ActionConfirmed(text, caption) = 
            MessageBox.Show(text, caption, MessageBoxButton.YesNo, MessageBoxImage.Question) = MessageBoxResult.Yes

        static member Error(text, ?caption) = 
            let caption' = defaultArg caption "Error!!!"
            MessageBox.Show(text, caption', MessageBoxButton.OK, MessageBoxImage.Error) |> ignore

    let isAdmin() = 
        [| "MITEKM-PC"; "STEVESALES-HP" |] |> Array.exists(Environment.MachineName.ToUpper().Equals)


module ObservableCollection =
    
    open System.Collections.Specialized
    open System.Linq

    let deleteEntityOnRemove(repository : DataModel.Repository) (xs : ObservableCollection<#EntityModel<'a>>) = 
        xs.CollectionChanged.Add <| fun event -> 
            if event.Action = Collections.Specialized.NotifyCollectionChangedAction.Remove 
            then 
                let entity = event.OldItems.Cast<EntityModel<'a>>().Single().Entity
                if entity <> null then repository.DeleteObject entity
        xs

    let empty<'T> = ObservableCollection<'T>()
    let singleton x = ObservableCollection [ x ]

module List = 
    let ofButtonClicks (xs : (#ButtonBase * 'a) list) = xs |> List.map Button.ClickToValue
            

